// The NSpring Framework for .NET
// Copyright (c) 2003, Jeffrey Varszegi
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without modification, 
// are permitted provided that the following conditions are met:
// 
// - Redistributions of source code must retain the above copyright notice, this 
// list of conditions and the following disclaimer.
// 
// - Redistributions in binary form must reproduce the above copyright notice, this 
// list of conditions and the following disclaimer in the documentation and/or other 
// materials provided with the distribution.
// 
// - Neither the name of the NSpring project nor the names of its contributors may 
// be used to endorse or promote products derived from this software without 
// specific prior written permission from the copyright owner.
// 
// - No product derived from this software may be called "NSpring", nor may 
// "NSpring" appear in the name of such a product, without specific prior written 
// permission from the copyright owner.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
// OF THE POSSIBILITY OF SUCH DAMAGE. 

namespace NSpring.Logging {

using System;
using System.Collections;
using System.Globalization;
using System.Threading;

/// <summary>
/// <para>
/// A logging context contains system-dependent values such as the current system name, 
/// application name, and the current thread's name and priority, together with a set of
/// named values ("properties") designed to be set by program code; it is most easily understood
/// as the "header information" for a log message.   A Context instance
/// may be set on an Event and written to output, providing the ability to interleave log records
/// from different threads and application contexts without causing confusion.
/// </para>
/// <para>
/// Each thread is mapped to exactly one context, gettable via the static property CurrentContext.  
/// If contexts are enabled on a logger (see the Logger class' IsContextEnabled property), each Event
/// logged is provided with an immutable copy of the current thread's context via the Copy() method.
/// </para>
/// <para>
/// This logging context design is similar to that of the "diagnostic context" discussed by Neil Harrison in the book
/// "Pattern Languages of Program Design 3", article "Patterns for Logging Diagnostic Messages". 
/// </para>
/// </summary>
public abstract class Context {
    protected static string[] EMPTY_STRING_ARRAY        = {};
    private static string[] FILE_EXTENSIONS             = {".asp", ".aspx", ".dll", ".exe"};
    
    private static readonly string GUID                 = Guid.NewGuid().ToString().ToUpper().Substring(0, 16);

    protected string threadName;
    protected ThreadPriority threadPriority;
    protected CultureInfo culture;
    protected CultureInfo uiCulture;
    protected static string defaultSystemName           = Environment.MachineName;
    protected string systemName;
    protected string applicationName;
    protected string userName;
    
    /// <summary>
    /// Returns the context of the current thread
    /// </summary>
    public static Context CurrentContext {
        get {
            LocalDataStoreSlot ldss = Thread.GetNamedDataSlot(GUID);
            Context context = (Context)Thread.GetData(ldss);
            if (context == null) {
                context = new MutableContext();
                Thread.SetData(ldss, context);
            }
            return context;
        }
    }
    
    /// <summary>
    /// Indicates whether the data in this context-information object can be changed
    /// </summary>
    public abstract bool IsMutable {
        get;
    }
    
    /// <summary>
    /// The name of the application
    /// </summary>
    public string ApplicationName {
        get {
            return applicationName;
        }
        set {
            applicationName = value;
        }
    }
    
    /// <summary>
    /// The name of the system
    /// </summary>
    public string SystemName {
        get {
            return systemName;
        }
        set {
            systemName = value;
        }
    }
    
    /// <summary>
    /// The name of the current user
    /// </summary>
    public string UserName {
        get {
            return userName;
        }
        set {
            userName = value;
        }
    }
    
    /// <summary>
    /// Gets the name of the thread associated with this context
    /// </summary>
    public string ThreadName {
        get {
            return threadName;
        }
    }

    /// <summary>
    /// Gets the culture of the thread associated with this context
    /// </summary>
    public CultureInfo Culture {
        get {
            return culture;
        }
    }

    /// <summary>
    /// Gets the "UI culture" of the thread associated with this context
    /// </summary>
    public CultureInfo UICulture {
        get {
            return uiCulture;
        }
    }
    
    /// <summary>
    /// Gets the priority of the thread associated with this context
    /// </summary>
    public ThreadPriority ThreadPriority {
        get {
            return threadPriority;
        }
    }
    
    /// <summary>
    /// Gets the names of all properties associated with this logging thread context,
    /// in ascending order
    /// </summary>
    public abstract string[] PropertyNames {
        get;
    }
    
    /// <summary>
    /// Gets the number of properties associated with this logging thread context
    /// </summary>
    public abstract int PropertyCount {
        get;
    }
    
    /// <summary>
    /// Returns the property name at the specified index
    /// </summary>
    /// <param name="index">The index for which to return the property name</param>
    /// <returns>The property name associated with the specified index</returns>
    public abstract string GetPropertyName(int index);

    /// <summary>
    /// Returns the property at the specified index
    /// </summary>
    /// <param name="index">The index for which to return the property</param>
    /// <returns>The property associated with the specified index</returns>
    public abstract string GetProperty(int index);
    
    /// <summary>
    /// Returns the property with the given name
    /// </summary>
    /// <param name="propertyName">The name for which to return the property</param>
    /// <returns>The property associated with the specified name, or null if no such property exists</returns>
    public abstract string GetProperty(string propertyName);
    
    /// <summary>
    /// Resets system-dependent values, such as ThreadName, ThreadPriority, SystemName, UserName, and ApplicationName.  
    /// If the context is immutable, has no effect (no exception is thrown)
    /// </summary>
    public abstract void Refresh();
    
    /// <summary>
    /// Removes all properties.  
    /// If the context is immutable, has no effect (no exception is thrown)
    /// </summary>
    public abstract void ClearProperties();
    
    /// <summary>
    /// Removes the property with the given name.  
    /// If the context is immutable, has no effect (no exception is thrown)
    /// </summary>
    /// <param name="propertyName">The name of the property to remove</param>
    public abstract void RemoveProperty(string propertyName);
    
    /// <summary>
    /// Sets the value of the property with the given name.  
    /// If the context is immutable, has no effect (no exception is thrown)
    /// </summary>
    /// <param name="propertyName">The name of the property</param>
    /// <param name="propertyValue">The value to set on the property</param>
    public abstract void SetProperty(string propertyName, string propertyValue);    
    
    /// <summary>
    /// Creates an immutable copy of the context, which can then be set on an Event instance, etc.  This is necessary
    /// because the current thread's context may change between logging a message and the actual output of the message,
    /// and because the event may be output by a different thread from the one making the logging request.  This method,
    /// therefore, returns a "snapshot" of the context at a particular time
    /// </summary>
    /// <returns>An immutable copy of the context (or if the context is already immutable, the context itself)</returns>
    public abstract Context Copy();
    
    private sealed class MutableContext : Context {
        private Hashtable properties;
        private string[] propertyNames;
        private int propertyCount;
        private bool arePropertyNamesChanged;
        
        public MutableContext() {
            properties = new Hashtable(50, 0.5F);
            propertyNames = new string[50];
            propertyCount = 0;
            arePropertyNamesChanged = false;
        
            Thread thread = Thread.CurrentThread;
            threadName = thread.Name;
            culture = thread.CurrentCulture;
            uiCulture = thread.CurrentUICulture;
            threadPriority = thread.Priority;
            systemName = defaultSystemName;
            userName = Environment.UserName;
        
            applicationName = AppDomain.CurrentDomain.SetupInformation.ApplicationName;
            if (applicationName == null) {
                applicationName = Thread.GetDomain().FriendlyName;
                string applicationNameLowerCase = applicationName.ToLower();
                for(int x = 0; x < FILE_EXTENSIONS.Length; x++) {
                    if (applicationNameLowerCase.EndsWith(FILE_EXTENSIONS[x])) {
                        applicationName = applicationName.Substring(0, applicationName.Length - FILE_EXTENSIONS[x].Length);
                        break;
                    }
                }
            }
        }
        
        public override int PropertyCount {
            get {
                return propertyCount;
            }
        }

        public override string[] PropertyNames {
            get {
                if (arePropertyNamesChanged) {
                    if (propertyCount >= propertyNames.Length) {
                        propertyNames = new string[propertyCount * 2];
                    }
                    else {
                        Array.Clear(propertyNames, 0, propertyNames.Length);
                    }
                    if (propertyCount != 0) {
                        IEnumerator enumerator = properties.Keys.GetEnumerator();
                        int x = 0;
                        while (enumerator.MoveNext()) {
                            propertyNames[x++] = (string)enumerator.Current;
                        }
                        Array.Sort(propertyNames, 0, propertyCount);
                    }
                    arePropertyNamesChanged = false;
                }
                if (propertyCount == 0) {
                    return EMPTY_STRING_ARRAY;
                }
                else {
                    string[] returnValue = new string[propertyCount];
                    Array.Copy(propertyNames, 0, returnValue, 0, propertyCount);
                    return returnValue;
                }
            }
        }

        public override string GetPropertyName(int index) {
            if ((index < 0) || (index >= propertyCount)) {
                throw new IndexOutOfRangeException();
            }
            if (arePropertyNamesChanged) {
                if (propertyCount >= propertyNames.Length) {
                    propertyNames = new string[propertyCount * 2];
                }
                else {
                    Array.Clear(propertyNames, 0, propertyNames.Length);
                }
                if (propertyCount != 0) {
                    IEnumerator enumerator = properties.Keys.GetEnumerator();
                    int x = 0;
                    while (enumerator.MoveNext()) {
                        propertyNames[x++] = (string)enumerator.Current;
                    }
                    Array.Sort(propertyNames, 0, propertyCount);
                }
                arePropertyNamesChanged = false;
            }
            return propertyNames[index];
        }

        public override string GetProperty(int index) {
            if (index >= propertyCount) {
                throw new IndexOutOfRangeException();
            }
            if (arePropertyNamesChanged) {
                if (propertyCount >= propertyNames.Length) {
                    propertyNames = new string[propertyCount * 2];
                }
                else {
                    Array.Clear(propertyNames, 0, propertyNames.Length);
                }
                if (propertyCount != 0) {
                    IEnumerator enumerator = properties.Keys.GetEnumerator();
                    int x = 0;
                    while (enumerator.MoveNext()) {
                        propertyNames[x++] = (string)enumerator.Current;
                    }
                    Array.Sort(propertyNames, 0, propertyCount);
                }
                arePropertyNamesChanged = false;
            }
            return (string)properties[propertyNames[index]];
        }

        public override string GetProperty(string propertyName) {
            if (propertyName == null) {
                return null;
            }
            return (string)properties[propertyName];
        }

        public override void Refresh() {
            Thread thread = Thread.CurrentThread;
            threadName = thread.Name;
            culture = thread.CurrentCulture;
            uiCulture = thread.CurrentUICulture;
            threadPriority = thread.Priority;
            if (applicationName == null) {
                applicationName = Thread.GetDomain().FriendlyName;
                if (applicationName == null) {
                    applicationName = Thread.GetDomain().FriendlyName;
                    string applicationNameLowerCase = applicationName.ToLower();
                    for(int x = 0; x < FILE_EXTENSIONS.Length; x++) {
                        if (applicationNameLowerCase.EndsWith(FILE_EXTENSIONS[x])) {
                            applicationName = applicationName.Substring(0, applicationName.Length - FILE_EXTENSIONS[x].Length);
                            break;
                        }
                    }
                }
            }
            if (systemName == null) {
                systemName = Environment.MachineName;
            }
            if (userName == null) {
                userName = Environment.UserName;
            }
        }
        
        public override void ClearProperties() {
            properties.Clear();
            Array.Clear(propertyNames, 0, propertyCount);
            propertyCount = 0;
            arePropertyNamesChanged = false;
        }
        
        public override void RemoveProperty(string propertyName) {
            if (propertyName == null) {
                return;
            }
            if (properties.ContainsKey(propertyName)) {
                properties.Remove(propertyName);
                propertyCount--;
                arePropertyNamesChanged = true;
            }
        }
        
        public override void SetProperty(string propertyName, string propertyValue) {
            if (propertyName == null) {
                throw new ArgumentNullException();
            }
            if (propertyValue == null) {
                RemoveProperty(propertyName);
            }
            else {
                if (!properties.ContainsKey(propertyName)) {
                    arePropertyNamesChanged = true;
                    propertyCount++;
                }
                properties[propertyName] = propertyValue;
            }
        }

        public override Context Copy() {
            return new ImmutableContext(this);
        }

        public override bool IsMutable {
            get {
                return true;
            }
        }
    }
    
    private sealed class ImmutableContext : Context {
        private string[] propertyNames;
        private string[] propertyValues;

        public ImmutableContext(MutableContext context) {
            this.threadName = context.threadName;
            this.culture = context.culture;
            this.uiCulture = context.uiCulture;
            this.threadPriority = context.threadPriority;
            this.applicationName = context.applicationName;
            this.systemName = context.systemName;
            
            propertyNames = context.PropertyNames;
            if (propertyNames.Length == 0) {
                propertyValues = EMPTY_STRING_ARRAY;
            }
            else {
                propertyValues = new string[propertyNames.Length];
                for(int x = 0; x < propertyValues.Length; x++) {
                    propertyValues[x] = context.GetProperty(propertyNames[x]);
                }
            }
        }
        
        public override int PropertyCount {
            get {
                return propertyNames.Length;
            }
        }
        
        public override string[] PropertyNames {
            get {
                int length = propertyNames.Length;
                if (length == 0) {
                    return EMPTY_STRING_ARRAY;
                }
                else {
                    string[] returnValue = new string[length];
                    Array.Copy(propertyNames, 0, returnValue, 0, length);
                    return returnValue;
                }
            }
        }

        public override string GetPropertyName(int index) {
            return propertyNames[index];
        }
        
        public override string GetProperty(int index) {
            return propertyValues[index];
        }
        
        public override string GetProperty(string propertyName) {
            for(int x = 0; x < propertyNames.Length; x++) {
                if (propertyNames[x].Equals(propertyName)) {
                    return propertyValues[x];
                }
            }
            return null;
        }
        
        public override void Refresh() {}
        public override void ClearProperties() {}
        public override void RemoveProperty(string propertyName) {}
        public override void SetProperty(string propertyName, string propertyValue) {}
        
        public override Context Copy() {
            return this;
        }

        public override bool IsMutable {
            get {
                return false;
            }
        }
        
    }
    
}

}
